use crate::{
    cmd::{validate_command, CommandError, CommandExecutor, HGet, HGetAll, HSet},
    RespArray, RespFrame, RespMap, RespNull, SimpleString,
};

impl CommandExecutor for HGet {
    fn execute(self, backend: &crate::Backend) -> RespFrame {
        match backend.hget(&self.key, &self.field) {
            Some(value) => value,
            None => RespFrame::Null(RespNull),
        }
    }
}

impl CommandExecutor for HGetAll {
    fn execute(self, backend: &crate::Backend) -> RespFrame {
        match backend.hgetall(&self.key) {
            Some(value) => {
                let mut map = RespMap::new();
                for entry in value.iter() {
                    map.insert(entry.key().to_string(), entry.value().to_owned());
                }
                map.into()
            }
            None => RespFrame::Null(RespNull),
        }
    }
}

impl CommandExecutor for HSet {
    fn execute(self, backend: &crate::Backend) -> RespFrame {
        backend.hset(self.key, self.field, self.value);
        SimpleString::new("OK").into()
    }
}

impl TryFrom<RespArray> for HGet {
    type Error = CommandError;
    fn try_from(value: RespArray) -> Result<Self, Self::Error> {
        validate_command(&value, "hget", 2)?;
        match (&value[1], &value[2]) {
            (RespFrame::BulkString(ref key), RespFrame::BulkString(ref field)) => {
                match String::from_utf8(key.to_vec()) {
                    Ok(key) => Ok(HGet {
                        key,
                        field: String::from_utf8(field.to_vec())?,
                    }),
                    Err(_) => Err(CommandError::InvalidArgs(
                        "Get command must be have a key as the first argument".to_string(),
                    )),
                }
            }
            _ => Err(CommandError::InvalidCommand(
                "HGet command must have invalid key and field argument".to_string(),
            )),
        }
    }
}

impl TryFrom<RespArray> for HGetAll {
    type Error = CommandError;
    fn try_from(value: RespArray) -> Result<Self, Self::Error> {
        validate_command(&value, "hgetall", 1)?;
        match &value[1] {
            RespFrame::BulkString(ref key) => match String::from_utf8(key.to_vec()) {
                Ok(key) => Ok(HGetAll { key }),
                Err(_) => Err(CommandError::InvalidArgs(
                    "HGetAll command must be have a key as the first argument".to_string(),
                )),
            },
            _ => Err(CommandError::InvalidCommand(
                "HGetAll command have a Invalid key".to_string(),
            )),
        }
    }
}

impl TryFrom<RespArray> for HSet {
    type Error = CommandError;
    fn try_from(value: RespArray) -> Result<Self, Self::Error> {
        validate_command(&value, "hset", 3)?;
        match (&value[1], &value[2], &value[3]) {
            (RespFrame::BulkString(ref key), RespFrame::BulkString(ref field), value) => {
                match String::from_utf8(key.to_vec()) {
                    Ok(key) => Ok(HSet {
                        key,
                        field: String::from_utf8(field.to_vec())?,
                        value: value.clone(),
                    }),
                    Err(_) => Err(CommandError::InvalidArgs(
                        "Get command must be have a key as the first argument".to_string(),
                    )),
                }
            }
            _ => Err(CommandError::InvalidCommand(
                "Get command must have a BulkString as the first argument".to_string(),
            )),
        }
    }
}

#[cfg(test)]
mod tests {
    use bytes::BytesMut;

    use crate::{BulkString, RespDecode};

    use super::*;

    #[test]
    fn hget_try_from_array_test() -> anyhow::Result<()> {
        let mut buf = BytesMut::new();
        buf.extend_from_slice(b"*3\r\n$4\r\nhget\r\n$3\r\nmap\r\n$5\r\nhello\r\n");

        let frame = RespArray::decode(&mut buf)?;

        let ans: HGet = frame.try_into()?;

        assert_eq!(ans.key, "map");
        assert_eq!(ans.field, "hello");

        Ok(())
    }

    #[test]
    fn hgetall_try_from_array_test() -> anyhow::Result<()> {
        let mut buf = BytesMut::new();
        buf.extend_from_slice(b"*2\r\n$7\r\nhgetall\r\n$3\r\nmap\r\n");

        let frame = RespArray::decode(&mut buf)?;

        let ans: HGetAll = frame.try_into()?;

        assert_eq!(ans.key, "map");

        Ok(())
    }

    #[test]
    fn hset_try_from_array_test() -> anyhow::Result<()> {
        let mut buf = BytesMut::new();
        buf.extend_from_slice(b"*4\r\n$4\r\nhset\r\n$3\r\nmap\r\n$5\r\nhello\r\n$5\r\nworld\r\n");

        let frame = RespArray::decode(&mut buf)?;

        let ans: HSet = frame.try_into()?;

        assert_eq!(ans.key, "map");
        assert_eq!(ans.field, "hello");
        assert_eq!(ans.value, BulkString::new("world").into());
        Ok(())
    }
}
